/*
 * JSF Toolkit Component Framework
 * Copyright (C) 2007 Noah Sloan <iamnoah A-T gmail D0T com>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 */
package com.jsftoolkit.utils;

import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * (More or less) "Pretty Prints" XHTML.  
 * 
 * @author noah
 * 
 */
public class HtmlWriterContentHandler extends DefaultHandler {

	private final Writer writer;

	private String tab = "  ";

	private boolean elementClosed = true;

	private boolean wasWhitespace;

	private int openElementCount;

	private boolean newLine;

	private Set<String> blockElements = Utils.asSet("address", "blockquote",
			"center", "dir", "div", "dl", "dt", "dd", "fieldset", "form", "h1",
			"h2", "h3", "h4", "h5", "h6", "hr", "menu", "noscript", "ol", "p",
			"pre", "table", "ul", "li", "tbody", "td", "tfoot", "th", "thead",
			"tr", "button", "del", "ins", "map", "script");

	private Stack<String> openElements = new Stack<String>();

	/**
	 * 
	 * @param writer
	 *            the writer to write the XML to.
	 */
	public HtmlWriterContentHandler(Writer writer) {
		super();
		this.writer = writer;
	}

	/**
	 * 
	 * @param writer
	 *            the writer to write the XML to
	 * @param openElementCount
	 *            the number of open elements preceding the first event
	 */
	public HtmlWriterContentHandler(final Writer writer, int openElementCount) {
		this(writer);
		this.openElementCount = openElementCount;
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		try {
			openElements.push(qName);
			checkOpenTag();
			if (newLine) {
				indent();
			}
			elementClosed = false;
			writer.write('<');
			writer.write(qName);
			writer.write(' ');
			for (int i = 0; i < attributes.getLength(); i++) {
				writer.write(attributes.getQName(i));
				writer.write("=\"");
				writer.write(attributes.getValue(i));
				writer.write("\" ");
			}
			openElementCount++;
		} catch (IOException e) {
			throw new SAXException(e);
		}
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		try {
			checkOpenTag();
			if (newLine) {
				indent();
			}
			for (int i = start; i < start + length; i++) {
				if (!(Character.isWhitespace(ch[i]) && wasWhitespace)
						|| "pre".equalsIgnoreCase(openElements.peek())) {
					writer.write(ch[i]);
					if (ch[i] == '\n') {
						indent();
					}
				}
				wasWhitespace = Character.isWhitespace(ch[i]);
			}
			newLine = false;
		} catch (IOException e) {
			throw new SAXException(e);
		}
	}

	protected void checkOpenTag() throws IOException {
		if (!elementClosed) {
			writer.write('>');
			elementClosed = true;
			if (blockElements.contains(openElements.peek().toLowerCase())) {
				writer.write('\n');
				newLine = true;
			}
		}
	}

	private void indent() throws IOException {
		for (int i = 0; i < openElementCount; i++) {
			writer.write(tab);
		}
		newLine = false;
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		try {
			openElementCount--;
			openElements.pop();
			if (elementClosed) {
				if (blockElements.contains(qName.toLowerCase())) {
					writer.write('\n');
					indent();
				}
				writer.write("</");
				writer.write(qName);
				writer.write(">");
			} else {
				elementClosed = true;
				writer.write("/>");
			}
			if (blockElements.contains(qName.toLowerCase())) {
				writer.write('\n');
				newLine = true;
			}
		} catch (IOException e) {
			throw new SAXException(e);
		}
	}

	public int getOpenElementCount() {
		return openElementCount;
	}

	public void setOpenElementCount(int openElementCount) {
		this.openElementCount = openElementCount;
	}

	public String getTab() {
		return tab;
	}

	public void setTab(String tab) {
		this.tab = tab;
	}

}
